Skip to content

Cakephp 5 upgrade #327

Closed
wants to merge 5 commits into from
Closed

Conversation

Ioannis
Copy link
Contributor

@Ioannis Ioannis commented Aug 14, 2025

Important changes:

  • ORM: order to orderBy,

example:

- ->order(['EnrollmentFlowSteps.ordr' => 'ASC'])
+ ->orderBy(['EnrollmentFlowSteps.ordr' => 'ASC'])
  • ORM: get() now uses named parameters instead of positional

example:

- $linkObj = $linkTable->get($link->value, ['contain' => $contain]);
+ $linkObj = $linkTable->get($link->value, contain: $contain);
  • Returning values from event listeners / callbacks is deprecated. Use $event->setResult() instead or $event->stopPropogation() to just stop the event propagation.

example:

         if($controller->calculatePermission()) {
           - return true;
           + $event->setResult(true);
            return;
          }
  • pagination results need to be called through items

example:

   - while($rs->valid()) {
   -   $o = $rs->current();
   + while($rs->items()->valid()) {
   +   $o = $rs->items()->current();
  • use the AllowDynamicProperties for the classes that construct dynamic properties

example:

+ [\AllowDynamicProperties]
class CousController extends StandardController {
  • Loading a component is not enough. First we load the component and we return it afterwards

example

    // COmanage specific component that handles authn/z processintg
   - $this->loadComponent('RegistryAuth');
   + $this->RegistryAuth = $this->components()->get('RegistryAuth');

@Ioannis Ioannis force-pushed the cakephp-5-upgrade branch 3 times, most recently from 4511779 to d5209e3 Compare August 18, 2025 08:03
@Ioannis
Copy link
Contributor Author

Ioannis commented Aug 31, 2025

CakePHP 4 → 5 Upgrade Developer Guideline

Scope

  • This document summarizes required source changes and checks to migrate this repository from CakePHP 4.x to 5.x, based on CakePHP official upgrade notes and patterns detected in this codebase.
  • No code changes are applied by this document. Use it as a checklist and reference while performing the upgrade.

References

Repository-specific hotspots (found using IDE scan)

  • ORM order(): multiple usages in app/src and plugins
    • app/src/Lib/Traits/AutoViewVarsTrait.php: lines ~194, ~196
    • app/src/Model/Table/EnrollmentFlowsTable.php: ~204, ~215
    • app/src/Model/Table/NamesTable.php: ~350, ~359
    • app/src/Model/Table/ProvisioningTargetsTable.php: ~156, ~280
    • app/src/Lib/Util/PaginatedSqlIterator.php: ~171
    • app/src/Model/Table/AddressesTable.php: ~184
    • app/src/Model/Table/CousTable.php: ~271
    • app/src/Model/Table/GroupNestingsTable.php: ~180
    • app/src/Model/Table/GroupsTable.php: ~1118
    • app/src/Model/Table/IdentifierAssignmentsTable.php: ~201
    • app/src/Model/Table/PetitionsTable.php: ~769
    • app/src/Model/Table/PipelinesTable.php: ~568
    • app/src/Model/Table/PluginsTable.php: ~215
    • app/src/Model/Table/TrafficDetoursTable.php: ~110
    • Plugins (examples):
    • availableplugins/PipelineToolkit/.../PersonRoleMappersTable.php: ~112
    • plugins/CoreEnroller/.../AttributeCollectorsTable.php: ~445
    • plugins/CoreEnroller/.../EmailVerifiersTable.php: ~192
    • plugins/CoreEnroller/.../AttributeCollectorsCell.php: ~84
  • Pagination: using ResultSet methods directly
    • app/src/Controller/Component/RegistryAuthComponent.php: ~576 has while ($rs->valid())
    • local/logs/debug.log contains deprecation notices advising use of items()
  • get() named parameters vs positional
    • Widespread usage of ->get(…) across controllers/components/tables. Review all entity fetches using Table::get().
  • Dynamic properties
    • Classes extending Cake core types often allow dynamic properties. Add #[\AllowDynamicProperties] (or declare properties) to our classes that set properties dynamically.
  • Event listeners / callbacks
    • Audit any return values in events; use $event->setResult() or $event->stopPropagation().

Checklist and code patterns to change

  1. ORM: order() → orderBy()
  • CakePHP 5 deprecates Query::order() in favor of orderBy(). Replace everywhere in application code (do not change vendor files).
  • Before:
$query = $table->find()->order(['EnrollmentFlowSteps.ordr' => 'ASC']);
  • After:
$query = $table->find()->orderBy(['EnrollmentFlowSteps.ordr' => 'ASC']);
  • Notes:
    • For calls with a second boolean parameter (to overwrite ordering), use:
    • Before: ->order(['name' => 'ASC'], true);
    • After: ->orderBy(['name' => 'ASC'], true);
      • For simple sorting arrays (without explicit ASC/DESC), keep semantics identical:
    • Before: ->order(['Names.family', 'Names.given'])
    • After: ->orderBy(['Names.family', 'Names.given'])
  1. Table::get() named parameters
  • CakePHP 5 changes method signatures to use named parameters for optional args.
  • Before:
$entity = $Table->get($id, ['contain' => $contain, 'fields' => $fields]);
  • After:
$entity = $Table->get($id, contain: $contain, fields: $fields);
  • Common places:
    • Controllers, Components (eg RegistryAuthComponent), and Tables calling other tables via TableLocator.
  • Also review other methods that adopted named parameters, e.g., delete(), save(), findList options, etc., following the migration guide.
  1. Events: returning values from listeners/callbacks is deprecated
  • Do not return values to communicate results. Instead set on the event.
  • Before:
public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)
{
  if ($this->needsSkip($entity)) {
    return false; // or return true / return $response
  }
}
  • After:
public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)
{
  if ($this->needsSkip($entity)) {
    $event->setResult(false); // or set desired value
    $event->stopPropagation(); // optional: only if you intend to stop other listeners
    return; // explicitly return void
  }
}
  • Controller events similarly should use $event->setResult($value) and return; If the intent is only to halt propagation, call $event->stopPropagation().
  • Then, where you trigger or check event results, use $event->getResult().
  1. Pagination result iteration: use items()
  • In CakePHP 5.1+, calling Cake\ORM\ResultSet methods directly on paginated results is deprecated. Use items().
  • Before:
$rs = $this->paginate($query);
while ($rs->valid()) {
  $o = $rs->current();
  $rs->next();
}
  • After:
$rs = $this->paginate($query);
while ($rs->items()->valid()) {
  $o = $rs->items()->current();
  $rs->items()->next();
}
  • Repository hotspot: RegistryAuthComponent around line ~576.
  1. Dynamic properties: add #[AllowDynamicProperties] or declare properties
  • PHP 8.2 deprecates dynamic properties. Cake 5 runtime warns if you set undeclared properties on app classes.
  • Options per class:
    a) Declare the property explicitly: public ?Type $Foo = null;
    b) Add attribute: #[\AllowDynamicProperties] above the class definition.
  • Before:
class CousController extends StandardController {
    // somewhere: $this->SomeTable = $this->fetchTable('Some');
}
  • After:
#[\AllowDynamicProperties]
class CousController extends StandardController {
    // or declare public properties explicitly
}
  • Where to apply:
    • Controllers, Components, Cells, Helpers, Table classes, Behaviors, and any custom classes where new properties are assigned dynamically (eg via ModelAware/ModelAwareTrait setModelClass, $this->Foo = ...).
  • Tip: Review log notice “Dynamic properties will be removed in PHP 8.2” and code using $this->X = … assignments.
  1. Other frequent Cake 4 → 5 adjustments
  • Query builder method renames/behavior tweaks:
    • group() → groupBy() also exists; check if used in your code and upgrade similarly.
    • Having/order direction expressions still work; but favor Expressions API consistently.
  • Controller::redirect() and Response changes: ensure type checks use Psr\Http\Message\ResponseInterface where appropriate.
  • MiddlewareQueue signatures and callable types may be stricter; update type hints.
  • Command/Shell migrations: Shell is removed/replaced by Command; if custom Shells exist in repo, migrate to Command classes.
  • Http exceptions and status methods often now return new immutable responses; avoid mutating Response in place.

Search-and-Replace plan (do not apply to vendor/):

  1. order( → orderBy(
    • Scope: app/src, app/plugins, app/availableplugins
    • Exclude: app/vendor
    • Manually verify usages with second overwrite parameter.
  2. get( calls with options array → named params
    • Pattern: $Table->get($id, [ ... ])
    • Replace with $Table->get($id, optionName: value, …)
    • Common option names: contain, fields, conditions, lock, forUpdate
  3. Event listeners returning values
    • Replace return true/false/value in event handlers with $event->setResult(value); and return; optionally stopPropagation().
    • Check beforeFilter, beforeRender, beforeRedirect, beforeSave, beforeFind, etc.
  4. Paginator result iteration
    • Replace $rs->valid()/current()/next() with $rs->items()->… or foreach ($rs as $row).
  5. Dynamic properties
    • For classes assigning properties dynamically, add #[\AllowDynamicProperties] or declare properties.

Examples from this repo (before → after)

  • EnrollmentFlowSteps ordering:
->order(['EnrollmentFlowSteps.ordr' => 'ASC'])
→ ->orderBy(['EnrollmentFlowSteps.ordr' => 'ASC'])
  • Petitions/steps chain:
->order(['EnrollmentFlowSteps.ordr'])
→ ->orderBy(['EnrollmentFlowSteps.ordr'])
  • Names natural sort:
->order(['Names.family', 'Names.given', 'Names.middle'])
→ ->orderBy(['Names.family', 'Names.given', 'Names.middle'])
  • Overwriting order:
->order(['name' => 'ASC'], true)
→ ->orderBy(['name' => 'ASC'], true)
  • Table::get with contain:
$linkObj = $linkTable->get($link->value, ['contain' => $contain]);
→ $linkObj = $linkTable->get($link->value, contain: $contain);
  • Pagination iteration:
while($rs->valid()) { $o = $rs->current(); /* … */ }
→ while($rs->items()->valid()) { $o = $rs->items()->current(); /* … */ }
  • Event listener pattern:
if ($controller->calculatePermission()) {
  // return true; (deprecated)
  $event->setResult(true);
  return;
}
  • Dynamic properties:
#[\AllowDynamicProperties]
class CousController extends StandardController { }

@Ioannis Ioannis requested review from benno and arlen August 31, 2025 08:57
@Ioannis
Copy link
Contributor Author

Ioannis commented Sep 1, 2025

Bumped to 5.2.7

Copy link
Contributor

@benno benno left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't see anything after the vendor directory because github won't show me more than 3000 files, but that should just be the webroot directory (which I probably don't have much to say about anyway). If there's something in there that I should look at let me know via another channel.

@@ -0,0 +1,11 @@
# LdapConnector plugin for CakePHP
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LdapConnector is not currently a thing so this plugin should probably not be in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -44,6 +44,7 @@
use Cake\ORM\TableRegistry;
use Cake\Utility\Hash;
use InvalidArgumentException;
use josegonzalez\Dotenv\Filter\NullFilter;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

Comment on lines +60 to +64
protected $RegistryAuth = null;
protected $Breadcrumb = null;

protected $Flash = null;
protected $FormProtection = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be reinventing a syntax Cake is moving away from. Is this just so we don't have to fix a bunch of references?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found the issue and fixed it. The issue was

-    if(isset($this->RegistryAuth)) {
+    if ($this->components()->has('RegistryAuth')) {

@@ -33,8 +33,9 @@
use Cake\Log\Log;
//use \App\Lib\Enum\PermissionEnum;

#[\AllowDynamicProperties]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this because of stuff we do, stuff Cake does, or both?

Given that dynamic properties are deprecated, do we need a (separate) plan for getting rid of them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably what we do. I am not sure if we can easily remove the decorator. I need to do more testing. Currently i am using it in 5 files.

use Cake\Datasource\EntityInterface;
use Cake\Event\Event;
use Cake\Event\EventListenerInterface;
use Cake\ORM\RulesChecker;
use Cake\ORM\TableRegistry;
use Cake\Utility\Inflector;
use PHPUnit\Event\RuntimeException;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we using PHPUnit\Event\RuntimeException and not just \RuntimeException?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

False import.

@@ -29,16 +29,20 @@

namespace App\Lib\Events;

use App\Lib\Traits\LabeledLogTrait;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that we have to declare the trait inside the class, what's the benefit of having a second declaration here that specifies the path? ie: Why are two lines better than one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.

Comment on lines 116 to 117
// The documentation is ambiguous as to whether or not we need to return $rules.
// The API docs say yes but the example doesn't have it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment no longer seems relevant?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

return;
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting rid of this else clause makes it less clear that there are exactly two non-overlapping cases being handled here. Is there something else this change is supposed to be doing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no. reverting

@@ -186,6 +186,7 @@ public function validationDefault(Validator $validator): Validator {
// Note that adopting an EIS Record briefly creates a second EIS Record for the same
// source key, so we shouldn't try to enforce uniqueness of the source key here.
// (See ExternalIdentitiesTable::adopt.)
// XXX Resyncing breaks things for OrcidSource
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this supposed to be committed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no. removed

@Ioannis Ioannis marked this pull request as draft September 9, 2025 15:02
@Ioannis Ioannis closed this Sep 11, 2025
@Ioannis Ioannis deleted the cakephp-5-upgrade branch September 11, 2025 14:34
Sign in to join this conversation on GitHub.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants